package simpledb;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * BufferPool manages the reading and writing of pages into memory from
 * disk. Access methods call into it to retrieve pages, and it fetches
 * pages from the appropriate location.
 * <p>
 * The BufferPool is also responsible for locking;  when a transaction fetches
 * a page, BufferPool which check that the transaction has the appropriate
 * locks to read/write the page.
 */
public class BufferPool {

    /** Bytes per page, including header. */
    public static final int PAGE_SIZE = 4096;

    /** Default number of pages passed to the constructor. This is used by
    other classes. BufferPool should use the numPages argument to the
    constructor instead. */
    public static final int DEFAULT_PAGES = 50;


    int numPages = 0 ;
    private List<Page> pageList ;

    /**
     * Creates a BufferPool that caches up to numPages pages.
     *
     * @param numPages maximum number of pages in this buffer pool.
     */
    public BufferPool(int numPages) {
        // some code goes here
        this.numPages = numPages ;
        pageList = new ArrayList(numPages)  ;
    }

    /**
     * Retrieve the specified page with the associated permissions.
     * Will acquire a lock and may block if that lock is held by another
     * transaction.
     * <p>
     * The retrieved page should be looked up in the buffer pool.  If it
     * is present, it should be returned.  If it is not present, it should
     * be added to the buffer pool and returned.  If there is insufficient
     * space in the buffer pool, an page should be evicted and the new page
     * should be added in its place.
     *
     * @param tid the ID of the transaction requesting the page
     * @param pid the ID of the requested page
     * @param perm the requested permissions on the page
     */
    public  Page getPage( TransactionId tid , PageId pid , Permissions perm )
        throws TransactionAbortedException, DbException {

        // some code goes here
        Page page  =  null ;
        if( perm.toString() != "UNKNOWN" ) {

            for (int i = 0; i < this.numPages ; i++) {
                if( pageList != null && pageList.size() >0  ){

                    if( i < pageList.size() ){
//                        System.out.print(" 缓存中找ing..." + pid.getTableId() + "===" +  pid.pageno() + " pageList ==" + pageList.get(i).getId().getTableId() + "===" + pageList.get(i).getId().pageno() );
                        if( pid.equals( pageList.get(i).getId() )){
                            return  pageList.get(i);
                        }
                    }
                }
//                break ;
            }
            System.out.println("缓存中没有找到，从磁盘中读取 getTableId ..." + pid.getTableId() + "-- pageno --" + pid.pageno() );

            DbFile dbFile = Database.getCatalog().getDbFile( pid.getTableId()  ) ;
            if( dbFile != null ){
                if( pageList.size() <= numPages ){
                    System.out.println( " pageList <=  numPages , add page . Or evict ... " );
                    page = dbFile.readPage(pid) ;
                    pageList.add( page );
                }
            }

        }

        return page ;
    }

    /**
     * Releases the lock on a page.
     * Calling this is very risky, and may result in wrong behavior. Think hard
     * about who needs to call this and why, and why they can run the risk of
     * calling it.
     *
     * @param tid the ID of the transaction requesting the unlock
     * @param pid the ID of the page to unlock
     */
    public  void releasePage(TransactionId tid, PageId pid) {
        // some code goes here
        // not necessary for lab1|lab2

    }

    /**
     * Release all locks associated with a given transaction.
     *
     * @param tid the ID of the transaction requesting the unlock
     */
    public  void transactionComplete(TransactionId tid) throws IOException {
        // some code goes here
        // not necessary for lab1|lab2
    }

    /** Return true if the specified transaction has a lock on the specified page */
    public   boolean holdsLock(TransactionId tid, PageId p) {
        // some code goes here
        // not necessary for lab1|lab2
        return false;
    }

    /**
     * Commit or abort a given transaction; release all locks associated to
     * the transaction.
     *
     * @param tid the ID of the transaction requesting the unlock
     * @param commit a flag indicating whether we should commit or abort
     */
    public   void transactionComplete(TransactionId tid, boolean commit)
        throws IOException {
        // some code goes here
        // not necessary for lab1|lab2
    }

    /**
     * Add a tuple to the specified table behalf of transaction tid.  Will
     * acquire a write lock on the page the tuple is added to(Lock 
     * acquisition is not needed for lab2). May block if the lock cannot 
     * be acquired.
     *
     * Marks any pages that were dirtied by the operation as dirty by calling
     * their markDirty bit, and updates cached versions of any pages that have
     * been dirtied so that future requests see up-to-date pages.
     *
     * @param tid the transaction adding the tuple
     * @param tableId the table to add the tuple to
     * @param t the tuple to add
     */
    public  void insertTuple(TransactionId tid, int tableId, Tuple t)
        throws DbException, IOException, TransactionAbortedException {
        // some code goes here
        // not necessary for lab1

        DbFile dbFile = Database.getCatalog().getDbFile( tableId ) ;
        HeapFile heapFile = null;
        HeapPage heapPage = null ;

        int newPageNo = -1 ;

        if( dbFile != null )
                heapFile = (HeapFile) dbFile ;

        for( int i = 0 ; i < heapFile.numPages() ; i++) {

            HeapPageId heapPageId = new HeapPageId( tableId , i );
            try {
                Page page = Database.getBufferPool().getPage( tid , heapPageId, Permissions.READ_WRITE );
                if (page instanceof HeapPage) {
                    heapPage = (HeapPage) page ;
                    if( heapPage.getNumEmptySlots() > 0 ){
                        break ;
                    }else{
//                        如果没有空slot，并且已经是最后一页，创建新文件
                        if( i == heapFile.numPages()-1 ){
                            newPageNo = i+1 ;
                            heapFile.setAppendPage(true);
                        }
                    }
                }
            } catch (TransactionAbortedException e) {
                e.printStackTrace();
            } catch (DbException e) {
                e.printStackTrace();
            }
        }

        if( newPageNo != -1 ){
            System.out.println( " 文件已满，创建新 Page.....");

            HeapPageId heapPageId = new HeapPageId( tableId , newPageNo );

            int headerSize = heapFile.getHeaderSize(t) ;
            int dataSize = t.getTupleDesc().getSize() ;

            byte[] newData = new byte[ headerSize + dataSize ];

            for (int i = 0; i < t.getTupleDesc().numFields(); i++) {
                IntField field = (IntField)t.getField(i);
                byte[] tempData =  toHH( field.getValue() );
                newData[headerSize] =  tempData[0];
                newData[headerSize+1] =  tempData[1];
                newData[headerSize+2] =  tempData[2];
                newData[headerSize+3] =  tempData[3];
            }
            heapPage = new HeapPage( heapPageId , newData  ) ;
        }

        if( heapPage.isDirty() == null  ){

            heapPage.markDirty( true , tid  );
            heapPage.addTuple( t );
            heapFile.writePage( heapPage );

            if( heapFile.isAppendPage() )
                heapFile.setAppendPage(false);

            heapPage.markDirty( false , tid  );
        }

    }


    public static byte[] toHH(int n) {
        byte[] b = new byte[4];
        b[3] = (byte) (n & 0xff);
        b[2] = (byte) (n >> 8 & 0xff);
        b[1] = (byte) (n >> 16 & 0xff);
        b[0] = (byte) (n >> 24 & 0xff);
        return b;
    }



    /**
     * Remove the specified tuple from the buffer pool.
     * Will acquire a write lock on the page the tuple is removed from. May block if
     * the lock cannot be acquired.
     *
     * Marks any pages that were dirtied by the operation as dirty by calling
     * their markDirty bit.  Does not need to update cached versions of any pages that have
     * been dirtied, as it is not possible that a new page was created during the deletion
     * (note difference from addTuple).
     *
     * @param tid the transaction adding the tuple.
     * @param t the tuple to add
     */
    public  void deleteTuple(TransactionId tid, Tuple t)
        throws DbException, TransactionAbortedException {
        // some code goes here
        // not necessary for lab1

        PageId pageId = t.getRecordId().getPageId() ;
        Page page = Database.getBufferPool().getPage( tid , pageId, Permissions.READ_WRITE );

        HeapPage heapPage = null ;
        if ( page instanceof HeapPage) {
            heapPage = (HeapPage) page;

        }
        if( heapPage.isDirty() == null  ){

            heapPage.markDirty( true , tid  );
            heapPage.deleteTuple( t );
            try {
//                flushPage( pageId);
                flushAllPages();
            } catch (IOException e) {
                e.printStackTrace();
            }
            heapPage.markDirty( false , tid  );
        }
    }

    /**
     * Flush all dirty pages to disk.
     * NB: Be careful using this routine -- it writes dirty data to disk so will
     *     break simpledb if running in NO STEAL mode.
     */
    public synchronized void flushAllPages() throws IOException {
        // some code goes here
        // not necessary for lab1

        Map<String, DbFile>  fileMaps =  Database.getCatalog().getFile() ;

        for (Map.Entry<String, DbFile> entry : fileMaps.entrySet()) {
            String tableName = entry.getKey();
            HeapFile heapFile = (HeapFile) entry.getValue();

//            System.out.println( "::::::::: scan tableName : " + tableName );
            for( int i = 0 ; i < heapFile.numPages() ; i++) {
                HeapPageId heapPageId = new HeapPageId( heapFile.getId() , i);
//                flushPage(  heapPageId  );
                Page page = null;
                try {
                    page = getPage( new TransactionId() , heapPageId , Permissions.READ_WRITE );
                    heapFile.writePage( page );
                } catch (TransactionAbortedException e) {
                    e.printStackTrace();
                } catch (DbException e) {
                    e.printStackTrace();
                }
            }
        }

    }


    /** Remove the specific page id from the buffer pool.
        Needed by the recovery manager to ensure that the
        buffer pool doesn't keep a rolled back page in its
        cache.
    */
    public synchronized void discardPage(PageId pid) {
        // some code goes here
        // only necessary for lab5
    }

    /**
     * Flushes a certain page to disk
     * @param pid an ID indicating the page to flush
     */
    private synchronized  void flushPage(PageId pid) throws IOException {
        // some code goes here
        // not necessary for lab1

        TransactionId transactionId = new TransactionId() ;

        try {
            Page page = Database.getBufferPool().getPage( transactionId  , pid  , Permissions.READ_WRITE ) ;
//            HeapFile heapFile = null ;
            if( page.isDirty() != null  ){
                System.out.println( " :::::: heapPage.isDirty(), 暂时懒的实现 :::::: ");
//                heapFile.writePage( page );
            }

        } catch (TransactionAbortedException e) {
            e.printStackTrace();
        } catch (DbException e) {
            e.printStackTrace();
        }

    }

    /** Write all pages of the specified transaction to disk.
     */
    public synchronized  void flushPages(TransactionId tid) throws IOException {
        // some code goes here
        // not necessary for lab1|lab2|lab3
    }

    /**
     * Discards a page from the buffer pool.
     * Flushes the page to disk to ensure dirty pages are updated on disk.
     */
    private synchronized  void evictPage() throws DbException {
        // some code goes here
        // not necessary for lab1
    }

}
